package com.t235.gc.zuul.rbac;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.t235.gc.AjaxResult;
import com.t235.gc.JwtTokenUtils;
import io.jsonwebtoken.JwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.List;

import static com.t235.gc.PermissionUtils.isAdmin;

//@Component
@Slf4j
public class AuthFilter extends ZuulFilter {
    private RouteLocator routeLocator;
    @Resource
    private ObjectMapper objectMapper;
    @Resource
    private RbacService rbacService;
    @Value("${uaa.path.login}")
    private String loginPath;
    @Value("${uaa.path.menu}")
    private String userMenuPath;

    public AuthFilter(RouteLocator routeLocator) {
        this.routeLocator = routeLocator;
    }

    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
        // options直接放行
        if(HttpMethod.OPTIONS.matches(request.getMethod())) {
            return false;
        }
        // 登录
        if(isLogin(getRoute(request.getRequestURI()))) {
            return false;
        }
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
        // 原始token，带Bearer 前缀
        String rawToken = request.getHeader("Authorization");
        String requestURI = request.getRequestURI();
        Route route = getRoute(requestURI);
        if (log.isDebugEnabled()) {
            log.debug("method:{}, requestURI：{}, getRequestURI()：{},token:{}",
                    request.getMethod(), requestURI, request.getRequestURI(), rawToken);
            log.debug("route:{}", route);
        }
        // 没有token，需要登录
        if (!StringUtils.hasText(rawToken)) {
            writeError(HttpStatus.UNAUTHORIZED, "请先登录");
            return null;
        }
        // 验证token
        TokenValidator tokenValidator = new TokenValidator(rawToken).invoke();
        if (tokenValidator.isInvalid()) {
            return null;
        }
        // 存储在token中的角色名列表
        List<String> roles = tokenValidator.getRoles();
        // 去除Bearer 前缀后的token
        String token = tokenValidator.getToken();
        // 加载用户菜单只需要验证token，不验证权限
        if(isLoadUserMenu(route)) {
            return null;
        }
        // 超级管理员不验证权限
        if(isAdmin(roles)) {
            return null;
        }
        // 鉴权
        boolean hasPerm = rbacService.hasPermission(route.getPath(), String.join(",", roles), token);
        if (!hasPerm) {
            writeError(HttpStatus.FORBIDDEN, "zuul:无权访问，请联系管理员");
            return null;
        }
        return null;
    }

    private Route getRoute(String requestURI){
        return routeLocator.getMatchingRoute(requestURI);
    }

    private boolean isLoadUserMenu(Route route) {
        return userMenuPath.equals("/" + route.getLocation() + route.getPath());
    }

    private boolean isLogin(Route route) {
        return loginPath.equals("/" + route.getLocation() + route.getPath());
    }

    private void writeError(HttpStatus httpStatus, String errorMsg) {
        RequestContext ctx = RequestContext.getCurrentContext();
        ctx.getResponse().setStatus(httpStatus.value());
        ctx.setSendZuulResponse(false);
        ctx.getResponse().setContentType("application/json;charset=UTF-8");
        try {
            ctx.setResponseBody(objectMapper.writeValueAsString(AjaxResult.error(errorMsg)));
            ctx.getResponse().flushBuffer();
        } catch (JsonProcessingException e) {
            log.error(e.getMessage(), e);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
    }

    private class TokenValidator {
        private boolean invalid;
        private String token;
        private List<String> roles;

        public TokenValidator(String token) {
            this.token = token;
        }

        boolean isInvalid() {
            return invalid;
        }

        public List<String> getRoles() {
            return roles;
        }

        public String getToken() {
            return this.token;
        }

        public TokenValidator invoke() {
            token = token.replace(JwtTokenUtils.TOKEN_PREFIX, "");
            String username;
            try {
                username = JwtTokenUtils.getUsername(token);
                roles = JwtTokenUtils.getUserRole(token);
            } catch (JwtException ex) {
                log.error("JWT parse exception", ex);
                writeError(HttpStatus.INTERNAL_SERVER_ERROR, "JWT解析异常" + ex.getMessage());
                invalid = true;
                return this;
            }
            if (log.isDebugEnabled()) {
                log.debug("username:{},roles:{}", username, roles);
            }
            invalid = false;
            return this;
        }
    }
}
